#ifndef __box_H__
#define __box_H__

#include <algorithm>

namespace rend
{

template < unsigned DIMENSION_T >
class Box
{
public:

	Box()
	{}

	Box(const float (& minimum)[DIMENSION_T],
		const float (& maximum)[DIMENSION_T])
	: mMinimum(minimum)
	, mMaximum(maximum)
	{}

	const vect< DIMENSION_T >& getMinimum() const
	{ return mMinimum; }

	const vect< DIMENSION_T >& getMaximum() const
	{ return mMaximum; }

	void setMinimum(const float (& minimum)[DIMENSION_T])
	{ mMinimum = vect< DIMENSION_T >::cast(minimum); }

	void setMaximum(const float (& maximum)[DIMENSION_T])
	{ mMaximum = vect< DIMENSION_T >::cast(maximum); }

	void setExtents(const float (& minimum)[DIMENSION_T],
					const float (& maximum)[DIMENSION_T])
	{
		setMinimum(minimum);
		setMaximum(maximum);
	}

	void merge(const Box< DIMENSION_T >& box)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::min(mMinimum[i], box.mMinimum[i]));
			mMaximum.set(i, std::max(mMaximum[i], box.mMaximum[i]));
		}
	}

	void merge(const Box< DIMENSION_T >& box0,
			   const Box< DIMENSION_T >& box1)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::min(box0.mMinimum[i], box1.mMinimum[i]));
			mMaximum.set(i, std::max(box0.mMaximum[i], box1.mMaximum[i]));
		}
	}

	void merge(const float (& point)[DIMENSION_T])
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			if (point[i] < mMinimum[i])
				mMinimum.set(i, point[i]);
			else
			if (point[i] > mMaximum[i])
				mMaximum.set(i, point[i]);
		}
	}

	void intersect(const Box< DIMENSION_T >& box)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::max(mMinimum[i], box.mMinimum[i]));
			mMaximum.set(i, std::min(mMaximum[i], box.mMaximum[i]));
		}
	}

	void intersect(const Box< DIMENSION_T >& box0,
				   const Box< DIMENSION_T >& box1)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::max(box0.mMinimum[i], box1.mMinimum[i]));
			mMaximum.set(i, std::min(box0.mMaximum[i], box1.mMaximum[i]));
		}
	}

	bool isIntersecting(const Box< DIMENSION_T >& box) const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] >= box.mMaximum[i] ||
				mMaximum[i] <= box.mMinimum[i])
			{
				return false;
			}

		return true;
	}

	// Returns true if the box satisfies min <= max in every dimension
	bool isValid() const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] > mMaximum[i])
				return false;

		return true;
	}

	// Returns true if the box has zero volume, or is not valid
	bool isEmpty() const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] >= mMaximum[i])
				return true;

		return false;
	}

	// Return the span of the box along one axis
	float getSpan(const unsigned axis) const
	{
		assert(axis < DIMENSION_T);

		return mMaximum[axis] - mMinimum[axis];
	}

	// Return the span of the box along all axes
	void getSpan(vect< DIMENSION_T >& span) const
	{
		span.sub(mMaximum, mMinimum);
	}

	// Calculate the volume of this box
	float getVolume() const
	{
		vect< DIMENSION_T > span;
		getSpan(span);

		float vol = span[0];

		for (unsigned i = 1; i < DIMENSION_T; ++i)
			vol *= span[i];

		return vol;
	}

	// Tests whether the supplied point is within this box. 
	bool isInside(const float (& point)[DIMENSION_T]) const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (point[i] < mMinimum[i] || point[i] > mMaximum[i])
				return false;

		return true;
	}

	// Tests whether the supplied box is within this box.
	bool isInside(const Box< DIMENSION_T >& box) const
	{
		return
			isInside(box.getMinimum()) &&
			isInside(box.getMaximum());
	}

	// Gets the centre of the box.
	void getCentre(vect< DIMENSION_T >& centre) const
	{
		centre.add(mMinimum, mMaximum);
		centre.mul(.5f);
	}

	bool operator == (const Box< DIMENSION_T >& box) const 
	{
		return
			mMinimum == box.mMinimum &&
			mMaximum == box.mMaximum;
	}

protected:

	vect< DIMENSION_T > mMinimum;
	vect< DIMENSION_T > mMaximum;
};



class Box2 : public Box<2>
{
public:

	Box2()
	{}

	Box2(const float (& minimum)[2],
		 const float (& maximum)[2])
	: Box<2>(minimum, maximum)
	{}

	Box2(const float minX, const float minY,
		 const float maxX, const float maxY)
	{
		mMinimum = vect2(minX, minY);
		mMaximum = vect2(maxX, maxY);
	}

	//////////////////////////////////////////////////////////////
	const vect2& getMinimum() const
	{ return vect2::cast(mMinimum); }

	const vect2& getMaximum() const
	{ return vect2::cast(mMaximum); }

	//////////////////////////////////////////////////////////////
	float getWidth() const
	{ return getSpan(0); }

	float getHeight() const
	{ return getSpan(1); }

	//////////////////////////////////////////////////////////////
	float getLeft() const
	{ return mMinimum[0]; }

	float getRight() const
	{ return mMaximum[0]; }

	float getBottom() const
	{ return mMinimum[1]; }

	float getTop() const
	{ return mMaximum[1]; }
};


class Box3 : public Box<3>
{
public:

	Box3()
	{}

	Box3(const float (& minimum)[3],
		 const float (& maximum)[3])
	: Box<3>(minimum, maximum)
	{}

	Box3(const float minX, const float minY, const float minZ,
		 const float maxX, const float maxY, const float maxZ)
	{
		mMinimum = vect3(minX, minY, minZ);
		mMaximum = vect3(maxX, maxY, maxZ);
	}

	//////////////////////////////////////////////////////////////
	const vect3& getMinimum() const
	{ return vect3::cast(mMinimum); }

	const vect3& getMaximum() const
	{ return vect3::cast(mMaximum); }

	//////////////////////////////////////////////////////////////
	float getWidth() const
	{ return getSpan(0); }

	float getHeight() const
	{ return getSpan(1); }

	float getDepth() const
	{ return getSpan(2); }

	//////////////////////////////////////////////////////////////
	float getLeft() const
	{ return mMinimum[0]; }

	float getRight() const
	{ return mMaximum[0]; }

	float getBottom() const
	{ return mMinimum[1]; }

	float getTop() const
	{ return mMaximum[1]; }

	float getFront() const
	{ return mMinimum[2]; }

	float getBack() const
	{ return mMaximum[2]; }
};


//	Axis-aligned box, discrete version;
//	handles integer coordinates only; all intervals are open from the right

template < unsigned DIMENSION_T >
class BoxDiscrete
{
public:

	BoxDiscrete()
	{}

	BoxDiscrete(const int (& minimum)[DIMENSION_T],
				const int (& maximum)[DIMENSION_T])
	: mMinimum(minimum)
	, mMaximum(maximum)
	{}

	const ivect< DIMENSION_T >& getMinimum() const
	{ return mMinimum; }

	const ivect< DIMENSION_T >& getMaximum() const
	{ return mMaximum; }

	void setMinimum(const int (& minimum)[DIMENSION_T])
	{ mMinimum = ivect< DIMENSION_T >::cast(minimum); }

	void setMaximum(const int (& maximum)[DIMENSION_T])
	{ mMaximum = ivect< DIMENSION_T >::cast(maximum); }

	void setExtents(const int (& minimum)[DIMENSION_T],
					const int (& maximum)[DIMENSION_T])
	{
		setMinimum(minimum);
		setMaximum(maximum);
	}

	void merge(const BoxDiscrete< DIMENSION_T >& box)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::min(mMinimum[i], box.mMinimum[i]));
			mMaximum.set(i, std::max(mMaximum[i], box.mMaximum[i]));
		}
	}

	void merge(const BoxDiscrete< DIMENSION_T >& box0,
			   const BoxDiscrete< DIMENSION_T >& box1)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::min(box0.mMinimum[i], box1.mMinimum[i]));
			mMaximum.set(i, std::max(box0.mMaximum[i], box1.mMaximum[i]));
		}
	}

	void merge(const int (& point)[DIMENSION_T])
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			if (point[i] < mMinimum[i])
				mMinimum.set(i, point[i]);
			else
			if (point[i] >= mMaximum[i])
				mMaximum.set(i, point[i] + 1);
		}
	}

	void intersect(const BoxDiscrete< DIMENSION_T >& box)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::max(mMinimum[i], box.mMinimum[i]));
			mMaximum.set(i, std::min(mMaximum[i], box.mMaximum[i]));
		}
	}

	void intersect(const BoxDiscrete< DIMENSION_T >& box0,
				   const BoxDiscrete< DIMENSION_T >& box1)
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
		{
			mMinimum.set(i, std::max(box0.mMinimum[i], box1.mMinimum[i]));
			mMaximum.set(i, std::min(box0.mMaximum[i], box1.mMaximum[i]));
		}
	}

	bool isIntersecting(const BoxDiscrete< DIMENSION_T >& box) const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] >= box.mMaximum[i] ||
				mMaximum[i] <= box.mMinimum[i])
			{
				return false;
			}

		return true;
	}

	bool isValid() const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] > mMaximum[i])
				return false;

		return true;
	}

	bool isEmpty() const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (mMinimum[i] >= mMaximum[i])
				return true;

		return false;
	}

	int getSpan(const unsigned axis) const
	{
		assert(axis < DIMENSION_T);

		return mMaximum[axis] - mMinimum[axis];
	}

	void getSpan(ivect< DIMENSION_T >& span) const
	{
		span.sub(mMaximum, mMinimum);
	}

	int getVolume() const
	{
		ivect< DIMENSION_T > span;
		getSpan(span);

		int vol = span[0];

		for (unsigned i = 1; i < DIMENSION_T; ++i)
			vol *= span[i];

		return vol;
	}

	bool isInside(const int (& point)[DIMENSION_T]) const
	{
		for (unsigned i = 0; i < DIMENSION_T; ++i)
			if (point[i] < mMinimum[i] || point[i] >= mMaximum[i])
				return false;

		return true;
	}

	bool isInside(const BoxDiscrete< DIMENSION_T >& box) const
	{
		ivect< DIMENSION_T > decMax(box.getMaximum());

		for (unsigned i = 0; i < DIMENSION_T; ++i)
			decMax.set(i, decMax[i] - 1);

		return
			isInside(box.getMinimum()) &&
			isInside(decMax);
	}
    
	bool operator == (const BoxDiscrete< DIMENSION_T >& box) const 
	{
		return
			mMinimum == box.mMinimum &&
			mMaximum == box.mMaximum;
	}

protected:

	ivect< DIMENSION_T > mMinimum;
	ivect< DIMENSION_T > mMaximum;
};

class BoxDiscrete3 : public BoxDiscrete<3>
{
public:

	BoxDiscrete3()
	{}

	BoxDiscrete3(const int (& minimum)[3],
				 const int (& maximum)[3])
	: BoxDiscrete<3>(minimum, maximum)
	{}

	BoxDiscrete3(const int minX, const int minY, const int minZ,
				 const int maxX, const int maxY, const int maxZ)
	{
		mMinimum = ivect3(minX, minY, minZ);
		mMaximum = ivect3(maxX, maxY, maxZ);
	}

	//////////////////////////////////////////////////////////////
	const ivect3& getMinimum() const
	{ return ivect3::cast(mMinimum); }

	const ivect3& getMaximum() const
	{ return ivect3::cast(mMaximum); }

	//////////////////////////////////////////////////////////////
	int getWidth() const
	{ return getSpan(0); }

	int getHeight() const
	{ return getSpan(1); }

	int getDepth() const
	{ return getSpan(2); }

	//////////////////////////////////////////////////////////////
	int getLeft() const
	{ return mMinimum[0]; }

	int getRight() const
	{ return mMaximum[0]; }

	int getBottom() const
	{ return mMinimum[1]; }

	int getTop() const
	{ return mMaximum[1]; }

	int getFront() const
	{ return mMinimum[2]; }

	int getBack() const
	{ return mMaximum[2]; }
};

} // namespace rend

#endif // __box_H__
